Skip to content

harden: fail-closed API auth, narrow container exposure, patch deps#1

Merged
mattmacrocket merged 2 commits into
mainfrom
harden/controlplane-auth
Jun 22, 2026
Merged

harden: fail-closed API auth, narrow container exposure, patch deps#1
mattmacrocket merged 2 commits into
mainfrom
harden/controlplane-auth

Conversation

@mattmacrocket

@mattmacrocket mattmacrocket commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Security hardening of the SquirrelOps control plane. Two batches of work:

Batch 1: auth, container exposure, deps

  • Fail-closed auth: an unset token now returns HTTP 503 instead of allowing all traffic. Skipping auth requires the explicit CONTROLPLANE_API_AUTH_DISABLED=1 opt-out.
  • Constant-time token comparison (hmac.compare_digest).
  • HTTP query-string tokens removed (they leak into logs/history). WebSocket query tokens retained out of browser necessity.
  • WebSocket relay fails closed the same way as HTTP.
  • Compose hardening: bind API to 127.0.0.1, pass through auth vars, mount only the managed runtime repos instead of the whole workspace.
  • Tighten dashboard CSP connect-src (drop broad https:/wss: wildcards).
  • Bump next 14.2.12 -> 14.2.35 (fixes CVE-2025-29927 and other 14.2.x advisories).

Batch 2: token off the client (H3) + env-var spelling (L6)

  • BFF proxy: dashboard REST now goes through a same-origin server route (app/api/cp/[...slug]) that injects the token from a server-only env var. The token no longer ships in the client bundle (verified). WebSocket streams become opt-in via a dedicated, clearly client-visible token, with REST-polling fallback when unset.
  • Env-var spelling: CONTROLPLANE_* is now canonical; the historical misspelling CONTROLPANE_* still works as a deprecated alias, so "fixing the typo" can never silently disable auth.

Verification

  • 11/11 auth-behavior checks pass against the real app (no-config -> 503, disabled -> 200, token set -> 401/200 by header, query token rejected).
  • 7/7 config alias-resolution checks (canonical wins, legacy honored, int/bool aliases).
  • End-to-end BFF test: token injected upstream, GET/POST proxied, token absent from .next/static.
  • next build, py_compile, bash -n, docker compose config, and the CI self-check all pass.

Known residual (documented, not in this PR)

  • WebSocket auth in a browser is inherently client-visible without a custom WS-terminating proxy; mitigated by loopback binding and opt-in design.
  • Lower-severity items: OpenAPI docs exposure (now behind auth), error-detail verbosity, unlocked actions-state.json write.

🤖 Generated with Claude Code

mattmacrocket and others added 2 commits June 22, 2026 00:41
Security hardening of the control plane:

- API auth now fails closed: an unset CONTROLPANE_API_AUTH_TOKEN returns 503
  instead of allowing all traffic. Skipping auth requires the explicit
  CONTROLPANE_API_AUTH_DISABLED=1 opt-out.
- Token comparison is now constant-time (hmac.compare_digest).
- HTTP no longer accepts tokens via query string (they leak into logs/history);
  WebSocket query tokens are retained out of browser necessity.
- WebSocket relay fails closed the same way as HTTP.
- docker-compose: bind the API to loopback (127.0.0.1), pass through the auth
  token/disable vars, and mount only the managed runtime repos instead of the
  whole home tree.
- Tighten dashboard CSP connect-src (drop broad https:/wss: wildcards).
- Bump next 14.2.12 -> 14.2.35 (fixes CVE-2025-29927 and other 14.2.x advisories).
- CI self-check opts out of auth explicitly; docs updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…names

H3 - dashboard API token no longer ships to the browser:
- Add a same-origin server route (app/api/cp/[...slug]/route.ts) that proxies all
  REST calls to the control-plane API and injects the token from a server-only
  env var (CONTROLPLANE_API_TOKEN). Verified the token never appears in the
  client bundle.
- lib/api.ts and lib/controlplane.ts now call the same-origin BFF with no token.
- WebSocket streams (which browsers cannot send headers on) become opt-in via a
  dedicated, clearly client-visible NEXT_PUBLIC_CONTROLPLANE_WS_TOKEN; when unset
  the dashboard falls back to REST polling through the authenticated BFF.
- compose: dashboard reaches the API server-side over the compose network; bind
  the dashboard to loopback.

L6 - env var spelling:
- The canonical control-plane prefix is now CONTROLPLANE_. The historical
  misspelling CONTROLPANE_ is still honored as a deprecated alias, so correcting
  the spelling can never silently disable auth.
- Updated config resolution, the smoke harness, compose, CI, and docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mattmacrocket
mattmacrocket merged commit 2c91377 into main Jun 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant